Skip to content

feat(desktop): reorder pinned sessions by dragging within the sidebar - #70223

Open
SHL0MS wants to merge 1 commit into
NousResearch:mainfrom
SHL0MS:feat/desktop-pinned-reorder-drop
Open

SHL0MS wants to merge 1 commit into
NousResearch:mainfrom
SHL0MS:feat/desktop-pinned-reorder-drop

Conversation

@SHL0MS

@SHL0MS SHL0MS commented Jul 23, 2026

Copy link
Copy Markdown

What does this PR do?

Two related asks for the pinned-sessions sidebar: reorder pinned chats, and let the pinned section grow past ~6 rows.

Reorder (the substantive half)

Reordering pinned rows was effectively broken. The pinned list had a reorder affordance on a near-invisible dnd-kit grab handle (the leading status dot), but commit ac4f596ca2 made the whole row body a pointer session-drag source (drag a row onto the chat to link it as an @session ref, or onto a zone edge to split). The two drag systems fought on the same row and session-drag won: grabbing a pin started a link-into-chat drag, and even when you found the handle its dotted focus ring appeared but the reorder never committed.

Fixed by resolving the conflict the way the session-drag engine already works — by drop location, not by which pixel you grab. The engine is a resolver: it snapshots targets at drag engage, hit-tests the pointer each move, and acts on release. This adds sidebar reorder as one more resolved target:

  • reorder-zones.ts — a small registry a flat list joins while mounted (container element + live id getter + onReorder). The drag snapshots registered zones at engage; resolveReorderTarget picks the insertion slot by pointer-Y (skipping the dragged row); reorderIds applies it with a no-op-identity guard. Both are pure functions, unit-tested.
  • session-drag.ts — resolveMove checks reorder first (inside the bar's bounds it wins), otherwise falls through to the existing stack/split/link zone logic unchanged; onCommit reorders in place instead of opening a tile or link. A dedicated $sidebarReorderHint atom drives the insertion line so the chat surfaces never re-render on reorder churn (they subscribe to the heavy $dropHint only for real link/split hints).
  • reorder-zone-list.tsx — registers the zone, tags rows (data-reorder-row) for the geometry snapshot, and paints the insertion line. No dnd-kit handle. The pinned section renders through it (reorderViaDrag) instead of ReorderableList.

Net behavior: drag a pin within the sidebar → reorder; drag it onto the chat → link (unchanged). Recents/Sessions rows keep session-drag exactly as before. This is the model the reporter asked for and it matches how the engine was built to route.

Height

The pinned body was hard-capped at max-h-44 (~6 rows) then scrolled. Relaxed to max-h-[40vh] so more pins show on a tall window. Still bounded, so the Sessions section below can never be buried — preserving the #43147 anti-overlap invariant — and GROUP_BODY still flattens the cap in compact mode.

Related Issue

Fixes the reorder half of #47728 (closed implemented-on-main, but reorder never actually worked once row-body session-drag landed — the handle was shadowed by the link drag). The height cap is the other half of the reporter's ask.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (the height relaxation + drop-location routing)

How was this tested?

  • Unit (reorder-zones.test.ts, 11 cases): slot-boundary math, dragged-row skip, foreign-drop rejection (a non-pinned row over the zone is not a reorder), multi-zone hit-testing, and reorderIds no-op identity + end-insertion.
  • Component (reorder-zone-list.test.tsx, 4 cases): zone register/unregister lifecycle, live-id reporting across re-render, per-row tagging, and insertion-line rendering only for a drag of the list's own row.
  • Real pointer-drag e2e (pinned-reorder.spec.ts): creates two pinned sessions, drags the top row down within the sidebar via actual mouse.down/move/up (stepped, like tile-unread-bug.spec), asserts the pinned order flips. Ran locally against the packaged macOS app — passes (1 passed (7.7s)).
  • npx tsc -p . / tsconfig.electron.json / tsconfig.e2e.json all clean; full renderer suite 2050 passed; eslint clean on touched files (test-file document warnings match existing test conventions).

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature
  • I've run the desktop renderer suite (npx vitest run --project ui) and all tests pass; pytest N/A (no Python changes)
  • I've added tests for my changes (unit + component + real-drag e2e)
  • I've tested on my platform: macOS 26.5 (arm64), packaged app

Documentation & Housekeeping

  • Docs — N/A (no user-facing doc describes the pinned drag/height behavior)
  • cli-config.yaml.example — N/A (no config keys)
  • CONTRIBUTING.md / AGENTS.md — N/A
  • Cross-platform impact considered — renderer + shared drag engine; the max-h-[40vh] and pointer-drag path are platform-agnostic. The e2e was exercised on macOS.
  • Tool descriptions/schemas — N/A

The pinned list had a reorder affordance on a near-invisible dnd-kit
grab handle (the leading status dot), but commit ac4f596 made the
whole row body a pointer session-drag source (drag onto chat to link /
split). The two drag systems fought on the same row and session-drag
won: grabbing a pin started a link-into-chat drag, and the handle's
dotted focus ring appeared but reorder never committed.

Resolve it the way the session-drag engine already works — by DROP
LOCATION, not by which pixel you grab. The engine is a resolver:
snapshot targets at engage, hit-test the pointer, act on release. This
adds sidebar reorder as one more resolved target:

- reorder-zones.ts: a small registry a flat list joins while mounted
  (container el + live ids + onReorder). The drag snapshots registered
  zones at engage; resolveReorderTarget picks the insertion slot by
  pointer-Y (skipping the dragged row); reorderIds applies it with a
  no-op guard. Both are pure and unit-tested.
- session-drag.ts: resolveMove checks reorder FIRST (inside the bar it
  wins), else falls through to the existing stack/split/link zone
  logic; onCommit reorders in place instead of opening a tile/link.
  A dedicated  atom drives the insertion line so
  chat surfaces never re-render on reorder churn.
- reorder-zone-list.tsx: registers the zone, tags rows
  (data-reorder-row) for the geometry snapshot, and paints the
  insertion line — no dnd-kit handle. The pinned section renders
  through it (reorderViaDrag) instead of ReorderableList.

Net: drag a pin within the sidebar → reorder; drag it onto the chat →
link (unchanged). Recents/Sessions rows still use session-drag as
before. Fixes the reorder half of NousResearch#47728 (which was closed
implemented-on-main but never actually worked once row-body drag
landed).

Also relax the pinned section's fixed max-h-44 (~6 rows) to max-h-[40vh]
so more pins show on tall windows; still bounded so Sessions can't be
buried (the NousResearch#43147 anti-overlap invariant) and GROUP_BODY still
flattens it in compact mode.

Tests: reorder-zones (11 pure-logic cases: slot boundaries, dragged-row
skip, foreign-drop rejection, no-op identity), reorder-zone-list (zone
registration lifecycle, live ids, own-drag-only insertion line), and a
real-pointer-drag e2e (pinned-reorder.spec) asserting the order flips.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/desktop Electron desktop app (apps/desktop/*) area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state needs-decision Awaiting maintainer decision before any implementation labels Jul 23, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related: #43661 also changes Desktop sidebar drag/reorder behavior, while #66051 handles persisted manual pin order. This PR resolves the row-drag conflict through drop-location routing with separate reorder zones; the overlapping but different mechanisms need maintainer consolidation.

@Nicolas-Formenton

Copy link
Copy Markdown

FYI: opened #84984 (drag to reorder profile groups in the All-profiles sidebar). It also touches sessions-section.tsx/index.tsx but only the profile-mode groups branch, disjoint from the pinned-session path here — no behavioral overlap, likely trivial import-context conflicts if either merges first.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/desktop Electron desktop app (apps/desktop/*) needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants